package parser

import (
	"fmt"
	"go/ast"
	"go/token"
)

type htmlTag struct {
	tagName    string
	closingTag bool
	attr       []htmlAttr
}

type htmlAttr struct {
	key string
	val ast.Expr
}

func (p *parser) parseHTMLTag() *htmlTag {
	if p.trace {
		defer un(trace(p, "HTMLTag"))
	}
	t := new(htmlTag)
	p.next()
	if p.tok == token.QUO {
		t.closingTag = true
		p.next()
	}

	switch p.tok {
	case token.IDENT:
		t.tagName = p.lit
		p.next()
	case token.SELECT:
		t.tagName = "select"
		p.next()
	default:
		p.expect(token.IDENT)
		return t
	}

	for p.tok == token.IDENT || p.tok == token.TYPE || p.tok == token.FOR {
		key := p.lit
		if p.tok == token.TYPE {
			key = "type"
		}
		if p.tok == token.FOR {
			key = "for"
		}
		p.next()
		for p.tok == token.SUB {
			key += "-"
			p.next()
			if p.tok != token.IDENT {
				p.expect(token.IDENT)
				break
			}
			key += p.lit
			p.next()
		}
		var val ast.Expr
		if p.tok == token.ASSIGN {
			p.next()
			val = p.parseRhs(true)
		}
		t.attr = append(t.attr, htmlAttr{key, val})
	}

	p.expect(token.GTR)
	return t
}

var hypergolElement = &ast.SelectorExpr{
	X:   ast.NewIdent("hypergol"),
	Sel: ast.NewIdent("Element"),
}

var hypergolAttribute = &ast.SelectorExpr{
	X:   ast.NewIdent("hypergol"),
	Sel: ast.NewIdent("Attribute"),
}

var hypergolAttributeSlice = &ast.ArrayType{
	Elt: hypergolAttribute,
}

func htmlToFunction(startTag *htmlTag, children []ast.Expr) *ast.CallExpr {
	c := &ast.CallExpr{
		Fun: hypergolElement,
		Args: []ast.Expr{
			&ast.BasicLit{
				Kind:  token.STRING,
				Value: fmt.Sprintf("%q", startTag.tagName),
			},
		},
	}

	if len(startTag.attr) == 0 {
		c.Args = append(c.Args, ast.NewIdent("nil"))
	} else {
		attr := &ast.CompositeLit{
			Type: hypergolAttributeSlice,
		}
		for _, a := range startTag.attr {
			v := &ast.CompositeLit{}
			v.Elts = append(v.Elts, &ast.BasicLit{
				Kind:  token.STRING,
				Value: fmt.Sprintf("%q", a.key),
			})
			if a.val == nil {
				v.Elts = append(v.Elts, ast.NewIdent("true"))
			} else {
				v.Elts = append(v.Elts, a.val)
			}
			attr.Elts = append(attr.Elts, v)
		}
		c.Args = append(c.Args, attr)
	}

	c.Args = append(c.Args, children...)
	return c
}

func (p *parser) parseHTMLContents(startTag *htmlTag) []ast.Expr {
	if voidElements[startTag.tagName] {
		return nil
	}
	if p.trace {
		defer un(trace(p, "HTMLContents"))
	}
	var res []ast.Expr

	for {
		switch p.tok {
		case token.LSS:
			tagStart := p.pos
			tag := p.parseHTMLTag()
			if tag.closingTag {
				if tag.tagName != startTag.tagName {
					p.error(tagStart, fmt.Sprintf("expected </%s>, found </%s>", startTag.tagName, tag.tagName))
				}
				return res
			}
			childContents := p.parseHTMLContents(tag)
			res = append(res, htmlToFunction(tag, childContents))

		case token.SEMICOLON:
			p.next()

		default:
			oldPos := p.pos
			res = append(res, p.parseBinaryExpr(false, token.LowestPrec+1, true))
			if p.pos == oldPos {
				p.errorExpected(p.pos, "expression")
				p.next()
				return res
			}
		}
	}
}

var voidElements = map[string]bool{
	"area":    true,
	"base":    true,
	"br":      true,
	"col":     true,
	"command": true,
	"embed":   true,
	"hr":      true,
	"img":     true,
	"input":   true,
	"keygen":  true,
	"link":    true,
	"meta":    true,
	"param":   true,
	"source":  true,
	"track":   true,
	"wbr":     true,
}
